/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file    ota_app.c
  * @author  MCD Application Team
  * @brief   ota_app application definition.
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2025 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "app_common.h"
#include "log_module.h"
#include "app_ble.h"
#include "ll_sys_if.h"
#include "dbg_trace.h"
#include "ble.h"
#include "ota_app.h"
#include "ota.h"
#include "stm32_rtos.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stm32_timer.h"
#include "flash_driver.h"
#include "flash_manager.h"
#include "app_menu_cfg.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
typedef enum
{
  FILE_TYPE_USER_CONF = 0,
  FILE_TYPE_FW_APP,
} OTA_APP_FileType_t;

/* USER CODE END PTD */

typedef enum
{
  Conf_INDICATION_OFF,
  Conf_INDICATION_ON,
  /* USER CODE BEGIN Service2_APP_SendInformation_t */
  OTA_APP_No_Pending,
  OTA_APP_Pending,
  OTA_APP_Ready_Pending,
  /* USER CODE END Service2_APP_SendInformation_t */
  OTA_APP_SENDINFORMATION_LAST
} OTA_APP_SendInformation_t;

typedef struct
{
  OTA_APP_SendInformation_t     Conf_Indication_Status;
  /* USER CODE BEGIN Service2_APP_Context_t */
  uint32_t base_address;
  uint8_t sectors;
  uint32_t write_value[60];
  uint8_t  write_value_index;
  uint8_t  file_type;
  /* USER CODE END Service2_APP_Context_t */
  uint16_t              ConnectionHandle;
} OTA_APP_Context_t;

/* Private defines -----------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* External variables --------------------------------------------------------*/
/* USER CODE BEGIN EV */

/* USER CODE END EV */

/* Private macros ------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* Compute start address where the User Configuration shall be located */
#define USER_CFG_SLOT_START_SECTOR_INDEX      (CFG_SNVMA_START_SECTOR_ID - CFG_USER_DATA_SECTORS_NB)

/* Compute size in Page of Application slot */
#define APP_SLOT_PAGE_SIZE                    ((CFG_SNVMA_START_SECTOR_ID - CFG_USER_DATA_SECTORS_NB) - CFG_APPLICATION_SLOT_START_SECTOR_INDEX)

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
static OTA_APP_Context_t OTA_APP_Context;

uint8_t a_OTA_UpdateCharData[247];

/* USER CODE BEGIN PV */
static uint32_t size_left;
static uint32_t address_offset;

static void FM_WriteCallback (FM_FlashOp_Status_t Status);
static void FM_EraseCallback (FM_FlashOp_Status_t Status);

static FM_FlashOp_Status_t FM_WriteStatus;
static FM_CallbackNode_t FM_WriteStatusCallback = 
{
  /* Header for chained list */
  .NodeList = 
  {
    .next = NULL,
    .prev = NULL
  },
  /* Callback for request status */
  .Callback = FM_WriteCallback
};

static FM_FlashOp_Status_t FM_EraseStatus;
static FM_CallbackNode_t FM_EraseStatusCallback = 
{
  /* Header for chained list */
  .NodeList = 
  {
    .next = NULL,
    .prev = NULL
  },
  /* Callback for request status */
  .Callback = FM_EraseCallback
};
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
static void OTA_Conf_SendIndication(void);

/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Functions Definition ------------------------------------------------------*/
void OTA_Notification(OTA_NotificationEvt_t *p_Notification)
{
  /* USER CODE BEGIN Service2_Notification_1 */
  OTA_Data_t msg_conf;
  /* USER CODE END Service2_Notification_1 */
  switch(p_Notification->EvtOpcode)
  {
    /* USER CODE BEGIN Service2_Notification_Service2_EvtOpcode */
    case OTA_CONF_EVT:
    {
      /* The Remote notifies it has send all the data to be written in Flash */
      
      /* Decide now what to do after all the data has been written in Flash */
      switch(OTA_APP_Context.file_type)
      {
        case FILE_TYPE_FW_APP:
        {
          uint8_t boot_bank = 0;
          
          boot_bank = APPE_get_active_flash_bank();
          LOG_INFO_APP("Swapping booting flash bank, now booting on bank %d\n", boot_bank);
          
          if(APPE_check_FW_opposite_bank_validity() != 0)
          {
            APPE_swap_flash_bank();
            
            boot_bank = APPE_get_active_flash_bank();
            LOG_INFO_APP("after reset MCU will boot on bank %d\n", boot_bank);
            
            
            LOG_INFO_APP("Resetting MCU to boot a new configured bank\n");
            NVIC_SystemReset();
          }
          else
          {
            LOG_INFO_APP("Application present in opposite bank is not valid\n");
          }
        }
        break;
        default:
          break;
      }
    }
    break;

    case OTA_READY_EVT:
      break;
    /* USER CODE END Service2_Notification_Service2_EvtOpcode */

    case OTA_BASE_ADR_WRITE_NO_RESP_EVT:
      /* USER CODE BEGIN Service2Char1_WRITE_NO_RESP_EVT */
      {
        switch( ((OTA_Base_Addr_Event_Format_t*)(p_Notification->DataTransfered.p_Payload))->Command )
        {
          case OTA_STOP_ALL_UPLOAD:
            break;

          case OTA_USER_CONF_UPLOAD:
          {
            uint32_t first_valid_address, last_valid_address;
            FM_Cmd_Status_t error = FM_ERROR;

            OTA_APP_Context.file_type = FILE_TYPE_USER_CONF;
            ((uint8_t*)&OTA_APP_Context.base_address)[0] = (((uint8_t*)((OTA_Base_Addr_Event_Format_t*)(p_Notification->DataTransfered.p_Payload))->Base_Addr))[2];
            ((uint8_t*)&OTA_APP_Context.base_address)[1] = (((uint8_t*)((OTA_Base_Addr_Event_Format_t*)(p_Notification->DataTransfered.p_Payload))->Base_Addr))[1];
            ((uint8_t*)&OTA_APP_Context.base_address)[2] = (((uint8_t*)((OTA_Base_Addr_Event_Format_t*)(p_Notification->DataTransfered.p_Payload))->Base_Addr))[0];
            OTA_APP_Context.base_address |= FLASH_BASE;
            
            if(p_Notification->DataTransfered.Length > 4)
            {
              OTA_APP_Context.sectors = ((OTA_Base_Addr_Event_Format_t*)(p_Notification->DataTransfered.p_Payload))->Sectors;
            }
            else
            {
              OTA_APP_Context.sectors = 0;
            }
            
            OTA_APP_Context.write_value_index = 0;
            OTA_APP_Context.Conf_Indication_Status = OTA_APP_Ready_Pending;
            
            first_valid_address = FLASH_BASE + (USER_CFG_SLOT_START_SECTOR_INDEX * FLASH_PAGE_SIZE);
            last_valid_address = first_valid_address + (CFG_USER_DATA_SECTORS_NB * FLASH_PAGE_SIZE);
            
            if(((OTA_APP_Context.base_address & 0xF) == 0) &&
               (OTA_APP_Context.sectors <= CFG_USER_DATA_SECTORS_NB) &&
               (OTA_APP_Context.base_address >= first_valid_address) &&
               ( (OTA_APP_Context.base_address + ((OTA_APP_Context.sectors) * FLASH_PAGE_SIZE)) <= last_valid_address) )
            { /* Download address is 128 bits aligned */
              /* Size of file to download fit in user configuration slot */
              /* Download address is in the user configuration area */
              /* End download address fit in the download area */

              /* Clear events before start testing */
              UTIL_SEQ_ClrEvt ( 1 << CFG_IDLEEVT_FM_ERASE_CALLBACK_EVT_RSP_ID);
              
              Menu_fw_update_page_update(MENU_UPDATE_CODE_USER_CONF_START_ERASE);

              /* Erase targetted sectors */
              while(error != FM_OK)
              {
                /* Flash manager erase */
                if(OTA_APP_Context.sectors == 0)
                {
                  OTA_APP_Context.sectors = CFG_USER_DATA_SECTORS_NB;
                }
                error = FM_Erase((uint32_t)USER_CFG_SLOT_START_SECTOR_INDEX, 
                                 (uint32_t)(OTA_APP_Context.sectors),
                                 &FM_EraseStatusCallback);

                /* Check Erase result */
                if (error == FM_OK)
                {
                  /* Wait for Erase callback to be invoked */
                  UTIL_SEQ_WaitEvt ( 1 << CFG_IDLEEVT_FM_ERASE_CALLBACK_EVT_RSP_ID);

                  /* Clear events before start testing */
                  UTIL_SEQ_ClrEvt ( 1 << CFG_IDLEEVT_FM_ERASE_CALLBACK_EVT_RSP_ID);

                  /* Check status of Erase status. */
                  if (FM_EraseStatus != FM_OPERATION_COMPLETE)
                  {
                    error = FM_ERROR;
                    LOG_INFO_APP("OTA_USER_CONF_UPLOAD: FM_EraseStatus != FM_OPERATION_COMPLETE => FM_ERROR\n");
                  }
                }
                else if(error == FM_BUSY)
                {
                  /* Wait for Erase callback to be invoked */
                  UTIL_SEQ_WaitEvt ( 1 << CFG_IDLEEVT_FM_ERASE_CALLBACK_EVT_RSP_ID);
                  
                  /* Clear events before start testing */
                  UTIL_SEQ_ClrEvt ( 1 << CFG_IDLEEVT_FM_ERASE_CALLBACK_EVT_RSP_ID);
                }
                else
                {
                  LOG_INFO_APP("OTA_USER_CONF_UPLOAD: FM_ERROR\n");
                }
              } /* while(error != FM_OK) */

              /* Flash zone tagetted has been erased, now ready to receive a file to write it */
              a_OTA_UpdateCharData[0] = OTA_READY_TO_RECEIVE_FILE;
              msg_conf.p_Payload = a_OTA_UpdateCharData;
              msg_conf.Length = 1;
              OTA_UpdateValue(OTA_CONF, &msg_conf);
            }
            else
            {
              LOG_INFO_APP("OTA_USER_CONF_UPLOAD: Not ready to receive file, oversized\n");
              LOG_INFO_APP("OTA_USER_CONF_UPLOAD: First 128 bits aligned address to download should be: 0x%08X\n", first_valid_address);
              a_OTA_UpdateCharData[0] = OTA_NOT_READY_TO_RECEIVE_FILE;
              msg_conf.p_Payload = a_OTA_UpdateCharData;
              msg_conf.Length = 1;
              OTA_UpdateValue(OTA_CONF, &msg_conf);
            }
          }
          break;

          case OTA_APPLICATION_UPLOAD:
          {
            FM_Cmd_Status_t error = FM_ERROR;
            
            OTA_APP_Context.file_type = FILE_TYPE_FW_APP;

            /* retrieve base address where to flash */
            ((uint8_t*)&OTA_APP_Context.base_address)[0] = (((uint8_t*)((OTA_Base_Addr_Event_Format_t*)(p_Notification->DataTransfered.p_Payload))->Base_Addr))[2];
            ((uint8_t*)&OTA_APP_Context.base_address)[1] = (((uint8_t*)((OTA_Base_Addr_Event_Format_t*)(p_Notification->DataTransfered.p_Payload))->Base_Addr))[1];
            ((uint8_t*)&OTA_APP_Context.base_address)[2] = (((uint8_t*)((OTA_Base_Addr_Event_Format_t*)(p_Notification->DataTransfered.p_Payload))->Base_Addr))[0];
            OTA_APP_Context.base_address |= FLASH_BASE;
            
            /*  In OTA Dual Bank use case, erase all memory of opposite bank. Do not use sectors count received */ 
            OTA_APP_Context.sectors = FLASH_PAGE_NB;
            
            OTA_APP_Context.write_value_index = 0;
            OTA_APP_Context.Conf_Indication_Status = OTA_APP_Ready_Pending;
            
            /* In OTA Dual Bank use case, correct address to flash in only 0x08100000*/
            if(OTA_APP_Context.base_address == (FLASH_BASE + FLASH_BANK_SIZE))
            {
              /* Clear events before start testing */
              UTIL_SEQ_ClrEvt ( 1 << CFG_IDLEEVT_FM_ERASE_CALLBACK_EVT_RSP_ID);
              
              Menu_fw_update_page_update(MENU_UPDATE_CODE_APP_START_ERASE);

              /* Erase targetted sectors */
              while(error != FM_OK)
              {
                /* Flash manager Erase */
                error = FM_Erase((uint32_t)((OTA_APP_Context.base_address - FLASH_BASE) >> 13), 
                                 (uint32_t)(OTA_APP_Context.sectors),
                                 &FM_EraseStatusCallback);

                /* Check Erase result. */
                if (error == FM_OK)
                {
                  /* Wait for Erase callback to be invoked */
                  UTIL_SEQ_WaitEvt ( 1 << CFG_IDLEEVT_FM_ERASE_CALLBACK_EVT_RSP_ID);

                  /* Clear events before start testing */
                  UTIL_SEQ_ClrEvt ( 1 << CFG_IDLEEVT_FM_ERASE_CALLBACK_EVT_RSP_ID);

                  /* Check status of Erase. */
                  if (FM_EraseStatus != FM_OPERATION_COMPLETE)
                  {
                    error = FM_ERROR;
                    LOG_ERROR_APP("OTA_APPLICATION_UPLOAD: FM_EraseStatus != FM_OPERATION_COMPLETE => FM_ERROR\n");
                  }
                }
                else if(error == FM_BUSY)
                {
                  /* Wait for Erase callback to be invoked */
                  UTIL_SEQ_WaitEvt ( 1 << CFG_IDLEEVT_FM_ERASE_CALLBACK_EVT_RSP_ID);
                  
                  /* Clear events before start testing */
                  UTIL_SEQ_ClrEvt ( 1 << CFG_IDLEEVT_FM_ERASE_CALLBACK_EVT_RSP_ID);
                }
                else
                {
                  LOG_ERROR_APP("OTA_APPLICATION_UPLOAD: FM_ERROR\n");
                  LOG_ERROR_APP("OTA_APPLICATION_UPLOAD: Try to FM_Erase Number of sectors: %d from sector %d \n",
                               (uint32_t)(OTA_APP_Context.sectors),
                               (uint32_t)((OTA_APP_Context.base_address) >> 13));
                }
              } /* while(error != FM_OK) */

              /* Flash zone tagetted has been erased, now ready to receive a file to write it */
              
              Menu_fw_update_page_update(MENU_UPDATE_CODE_APP_START_FLASH);
              
              a_OTA_UpdateCharData[0] = OTA_READY_TO_RECEIVE_FILE;
              msg_conf.p_Payload = a_OTA_UpdateCharData;
              msg_conf.Length = 1;
              OTA_UpdateValue(OTA_CONF, &msg_conf);
            }
            else
            {
              LOG_WARNING_APP("OTA_APPLICATION_UPLOAD: Address to download is not correct, should be: 0x%08X\n", (FLASH_BASE + FLASH_PAGE_SIZE));
              a_OTA_UpdateCharData[0] = OTA_NOT_READY_TO_RECEIVE_FILE;
              msg_conf.p_Payload = a_OTA_UpdateCharData;
              msg_conf.Length = 1;
              OTA_UpdateValue(OTA_CONF, &msg_conf);
            }
          }
          break;

          case OTA_UPLOAD_FINISHED:
          {
            if(OTA_APP_Context.file_type == FILE_TYPE_FW_APP)
            {
              OTA_APP_Context.Conf_Indication_Status = OTA_APP_Pending;
              
              if(APPE_check_FW_opposite_bank_validity() == 1)
              {
                a_OTA_UpdateCharData[0] = OTA_REBOOT_CONFIRMED;
                
                Menu_fw_update_page_update(MENU_UPDATE_CODE_APP_END_SUCCESS);
              }
              else
              {
                a_OTA_UpdateCharData[0] = OTA_FW_NOT_VALID;
                
                Menu_fw_update_page_update(MENU_UPDATE_CODE_APP_END_FAIL);
              }
              msg_conf.p_Payload = a_OTA_UpdateCharData;              
              msg_conf.Length = 1;
              OTA_UpdateValue(OTA_CONF, &msg_conf);
            }
          }
          break;

          case OTA_CANCEL_UPLOAD:
          {
            LOG_INFO_APP("OTA_CANCEL_UPLOAD\n");
          }
          break;

          default:
            break;
        }
      }
      /* USER CODE END Service2Char1_WRITE_NO_RESP_EVT */
      break;

    case OTA_CONF_INDICATE_ENABLED_EVT:
      /* USER CODE BEGIN Service2Char2_INDICATE_ENABLED_EVT */
      LOG_INFO_APP("OTA_CONF_INDICATE_ENABLED_EVT\n");
      /* USER CODE END Service2Char2_INDICATE_ENABLED_EVT */
      break;

    case OTA_CONF_INDICATE_DISABLED_EVT:
      /* USER CODE BEGIN Service2Char2_INDICATE_DISABLED_EVT */
      LOG_INFO_APP("OTA_CONF_INDICATE_DISABLED_EVT\n");
      /* USER CODE END Service2Char2_INDICATE_DISABLED_EVT */
      break;

    case OTA_RAW_DATA_WRITE_NO_RESP_EVT:
      /* USER CODE BEGIN Service2Char3_WRITE_NO_RESP_EVT */
      {
      FM_Cmd_Status_t error = FM_ERROR;
      
      /**
       * Write in Flash the data received in the BLE packet
       */
      size_left = p_Notification->DataTransfered.Length;

      /**
       * For the flash manager the address of the data to be stored in FLASH shall be 32bits aligned
       * and the address where the data shall be written shall be 128bits aligned
       */
      memcpy( (uint8_t*)&OTA_APP_Context.write_value,
              ((OTA_Raw_Data_Event_Format_t*)(p_Notification->DataTransfered.p_Payload))->Raw_Data,
              size_left );

      /* Clear events before start testing */
      UTIL_SEQ_ClrEvt ( 1 << CFG_IDLEEVT_FM_WRITE_CALLBACK_EVT_RSP_ID);

      while(error != FM_OK)
      {  
        /* Flash manager Write */
        error = FM_Write ((uint32_t *)(&OTA_APP_Context.write_value[0]),
                          (uint32_t *)((OTA_APP_Context.base_address) + address_offset),
                          size_left >> 2,
                          &FM_WriteStatusCallback);

        /* Check Write result. */
        if (error == FM_OK)
        {
          /* Wait for Write callback to be invoked */
          UTIL_SEQ_WaitEvt ( 1 << CFG_IDLEEVT_FM_WRITE_CALLBACK_EVT_RSP_ID);

          /* Clear events before start testing */
          UTIL_SEQ_ClrEvt ( 1 << CFG_IDLEEVT_FM_WRITE_CALLBACK_EVT_RSP_ID);

          /* Check status of Write op. */
          if (FM_WriteStatus != FM_OPERATION_COMPLETE)
          {
            error = FM_ERROR;
            LOG_INFO_APP("OTA_RAW_DATA_ID: FM_WriteStatus != FM_OPERATION_COMPLETE => FM_ERROR\n");
          }
        }
        else if(error == FM_BUSY)
        {
          /* Wait for Write callback to be invoked */
          UTIL_SEQ_WaitEvt ( 1 << CFG_IDLEEVT_FM_WRITE_CALLBACK_EVT_RSP_ID);
          
          /* Clear events before start testing */
          UTIL_SEQ_ClrEvt ( 1 << CFG_IDLEEVT_FM_WRITE_CALLBACK_EVT_RSP_ID);
        }
        else
        {
          LOG_INFO_APP("OTA_RAW_DATA_ID: FM_ERROR\n");
        }
      } /* while(error != FM_OK) */

      /* Update write offset address for the next FLASH write */
      address_offset += size_left;
      }
      /* USER CODE END Service2Char3_WRITE_NO_RESP_EVT */
      break;

    default:
      /* USER CODE BEGIN Service2_Notification_default */

      /* USER CODE END Service2_Notification_default */
      break;
  }
  /* USER CODE BEGIN Service2_Notification_2 */

  /* USER CODE END Service2_Notification_2 */
  return;
}

void OTA_APP_EvtRx(OTA_APP_ConnHandleNotEvt_t *p_Notification)
{
  /* USER CODE BEGIN Service2_APP_EvtRx_1 */

  /* USER CODE END Service2_APP_EvtRx_1 */

  switch(p_Notification->EvtOpcode)
  {
    /* USER CODE BEGIN Service2_APP_EvtRx_Service2_EvtOpcode */

    /* USER CODE END Service2_APP_EvtRx_Service2_EvtOpcode */
    case OTA_CONN_HANDLE_EVT :
      /* USER CODE BEGIN Service2_APP_CONN_HANDLE_EVT */

      /* USER CODE END Service2_APP_CONN_HANDLE_EVT */
      break;

    case OTA_DISCON_HANDLE_EVT :
      /* USER CODE BEGIN Service2_APP_DISCON_HANDLE_EVT */

      /* USER CODE END Service2_APP_DISCON_HANDLE_EVT */
      break;

    default:
      /* USER CODE BEGIN Service2_APP_EvtRx_default */

      /* USER CODE END Service2_APP_EvtRx_default */
      break;
  }

  /* USER CODE BEGIN Service2_APP_EvtRx_2 */

  /* USER CODE END Service2_APP_EvtRx_2 */

  return;
}

void OTA_APP_Init(void)
{
  UNUSED(OTA_APP_Context);
  OTA_Init();

  /* USER CODE BEGIN Service2_APP_Init */
  FM_WriteStatus = FM_OPERATION_AVAILABLE;
  size_left = 0;
  address_offset = 0;
  /* USER CODE END Service2_APP_Init */
  return;
}

/* USER CODE BEGIN FD */
/**
 * Get Confiramation status
 */
uint8_t OTA_APP_GetConfStatus(void)
{
  return(OTA_APP_Context.Conf_Indication_Status);
}
/* USER CODE END FD */

/*************************************************************
 *
 * LOCAL FUNCTIONS
 *
 *************************************************************/
__USED void OTA_Conf_SendIndication(void) /* Property Indication */
{
  OTA_APP_SendInformation_t indication_on_off = Conf_INDICATION_OFF;
  OTA_Data_t ota_indication_data;

  ota_indication_data.p_Payload = (uint8_t*)a_OTA_UpdateCharData;
  ota_indication_data.Length = 0;

  /* USER CODE BEGIN Service2Char2_IS_1 */

  /* USER CODE END Service2Char2_IS_1 */

  if (indication_on_off != Conf_INDICATION_OFF)
  {
    OTA_UpdateValue(OTA_CONF, &ota_indication_data);
  }

  /* USER CODE BEGIN Service2Char2_IS_Last */

  /* USER CODE END Service2Char2_IS_Last */

  return;
}

/* USER CODE BEGIN FD_LOCAL_FUNCTIONS */

static void FM_WriteCallback (FM_FlashOp_Status_t Status)
{
  /* Update status */ 
  FM_WriteStatus = Status;

  /* Set event on Process request call */
  UTIL_SEQ_SetEvt ( 1 << CFG_IDLEEVT_FM_WRITE_CALLBACK_EVT_RSP_ID);
}

static void FM_EraseCallback (FM_FlashOp_Status_t Status)
{
  /* Update status */ 
  FM_EraseStatus = Status;

  /* Set event on Process request call */
  UTIL_SEQ_SetEvt ( 1 << CFG_IDLEEVT_FM_ERASE_CALLBACK_EVT_RSP_ID);
}
/* USER CODE END FD_LOCAL_FUNCTIONS */
